/**********************************************************************
 * $Id: mitab_middatafile.cpp,v 1.15 2010-10-12 19:02:40 aboudreault Exp $
 *
 * Name:     mitab_datfile.cpp
 * Project:  MapInfo TAB Read/Write library
 * Language: C++
 * Purpose:  Implementation of the MIDDATAFile class used to handle
 *           reading/writing of the MID/MIF files
 * Author:   Stephane Villeneuve, stephane.v@videotron.ca
 *
 **********************************************************************
 * Copyright (c) 1999, 2000, Stephane Villeneuve
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 **********************************************************************
 *
 * $Log: mitab_middatafile.cpp,v $
 * Revision 1.15  2010-10-12 19:02:40  aboudreault
 * Fixed incomplet patch to handle differently indented lines in mif files (gdal #3694)
 *
 * Revision 1.14  2006-01-27 13:54:06  fwarmerdam
 * fixed memory leak
 *
 * Revision 1.13  2005/10/04 19:36:10  dmorissette
 * Added support for reading collections from MIF files (bug 1126)
 *
 * Revision 1.12  2005/09/29 19:46:55  dmorissette
 * Use "\t" as default delimiter in constructor (Anthony D - bugs 1155 and 37)
 *
 * Revision 1.11  2004/05/20 13:50:06  fwarmerdam
 * Call CPLReadLine(NULL) in Close() method to clean up working buffer.
 *
 * Revision 1.10  2002/04/26 14:16:49  julien
 * Finishing the implementation of Multipoint (support for MIF)
 *
 * Revision 1.9  2002/04/24 18:37:39  daniel
 * Added return statement at end of GetLastLine()
 *
 * Revision 1.8  2002/04/22 13:49:09  julien
 * Add EOF validation in MIDDATAFile::GetLastLine() (Bug 819)
 *
 * Revision 1.7  2001/09/19 14:49:49  warmerda
 * use VSIRewind() instead of rewind()
 *
 * Revision 1.6  2001/01/22 16:03:58  warmerda
 * expanded tabs
 *
 * Revision 1.5  2000/01/15 22:30:44  daniel
 * Switch to MIT/X-Consortium OpenSource license
 *
 * Revision 1.4  1999/12/19 17:41:29  daniel
 * Fixed a memory leak
 *
 * Revision 1.3  1999/11/14 17:43:32  stephane
 * Add ifdef to remove CPLError if OGR is define
 *
 * Revision 1.2  1999/11/11 01:22:05  stephane
 * Remove DebugFeature call, Point Reading error, add IsValidFeature() to 
 * test correctly if we are on a feature
 *
 * Revision 1.1  1999/11/08 04:16:07  stephane
 * First Revision
 *
 *
 **********************************************************************/

#include "mitab.h"

/*=====================================================================
 *                      class MIDDATAFile
 *
 *====================================================================*/

MIDDATAFile::MIDDATAFile()
{
    Reset();
}

MIDDATAFile::~MIDDATAFile()
{
    Close();
}

void MIDDATAFile::Reset()
{
    m_fp = NULL;
    m_szLastRead[0] = '\0';
    m_szSavedLine[0] = '\0';
    m_pszDelimiter = "\t"; // Encom 2003 (was NULL)
    
    m_dfXMultiplier = 1.0;
    m_dfYMultiplier = 1.0;
    m_dfXDisplacement = 0.0;
    m_dfYDisplacement = 0.0;
}

void MIDDATAFile::SaveLine(const char *pszLine)
{
    if (pszLine == NULL)
    {
        m_szSavedLine[0] = '\0';
    }
    else
    {
        strncpy(m_szSavedLine,pszLine,MIDMAXCHAR);
    }
}

const char *MIDDATAFile::GetSavedLine()
{
    return m_szSavedLine;
}

int MIDDATAFile::Open(const char *pszFname, const char *pszAccess)
{
   if (m_fp)
   {
       return -1;
   }

    /*-----------------------------------------------------------------
     * Validate access mode and make sure we use Text access.
     *----------------------------------------------------------------*/
    if (EQUALN(pszAccess, "r", 1) && strchr(pszAccess, '+') != NULL)
    {
        m_eAccessMode = TABReadWrite;
        pszAccess = "rt+";
    }
    else if (EQUALN(pszAccess, "r", 1))
    {
        m_eAccessMode = TABRead;
        pszAccess = "rt";
    }
    else if (EQUALN(pszAccess, "w", 1))
    {
        m_eAccessMode = TABWrite;
        pszAccess = "wt";
    }
    else
    {
        return -1;
    }

    /*-----------------------------------------------------------------
     * Open file for reading
     *----------------------------------------------------------------*/
    m_pszFname = CPLStrdup(pszFname);
    m_fp = VSIFOpen(m_pszFname, pszAccess);

    if (m_fp == NULL)
    {
        CPLFree(m_pszFname);
        m_pszFname = NULL;
        return -1;
    }

    SetEof(VSIFEof(m_fp));
    return 0;
}

bool MIDDATAFile::IsOpened()
{
    return (m_fp != NULL);
}

int MIDDATAFile::Reopen(TABAccess eAccessMode)
{
    if (IsOpened() && m_eAccessMode == eAccessMode)
        return 0;
    char* pszFname = CPLStrdup(m_pszFname);

    int ret1 = Close();
    int ret2 = 0;

    const char* pszAccess;
    if (eAccessMode == TABRead)
        pszAccess = "r";
    else if (eAccessMode == TABWrite)
        pszAccess = "w";
    else
        pszAccess = "r+";

    ret2 = Open(pszFname, pszAccess);
    CPLFree(pszFname);

    return (ret1 == 0 && ret2 == 0 ? 0 : 1);
}

int MIDDATAFile::Rewind()
{
    if (m_fp == NULL)
        return -1;

    else
    {
        VSIRewind(m_fp);
        SetEof(VSIFEof(m_fp));
    }
    return 0;
}

int MIDDATAFile::GotoEnd()
{
    if (m_fp == NULL)
        return -1;

    else
    {
        VSIFSeek(m_fp, 0L, SEEK_END);
        SetEof(VSIFEof(m_fp));
    }
    return 0;
}

int MIDDATAFile::UpdateFeatureInternal(long nStartPos, long nEndPos, UpdateFeatureHandler handler, void* data, GInt32& nLengthOffset)
{
    if (m_fp == NULL || m_eAccessMode != TABReadWrite)
        return -1;

    if (handler == NULL)
        return -1;

    if (nStartPos < 0 || nEndPos < 0)
        return -1;

    VSIFSeek(m_fp, 0L, SEEK_END);
    long nFileLen = VSIFTell(m_fp);

    if (nStartPos >= nFileLen || nEndPos > nFileLen)
        return -1;

    // Check whether we are updating the last feature.
    if(nEndPos == nFileLen)
    {
        VSIFSeek(m_fp, nStartPos, SEEK_SET);
        int ret = handler(data);
        if (ret != 0)
            return -1;
    }
    else
    {
        // Create a temporary file to move data
        const char* pszTmpFname = CPLGenerateTempFilename(NULL);
        if (pszTmpFname == NULL)
            return -1;

        FILE* fp;
        if ((fp = VSIFOpen(pszTmpFname, "wt+")) == NULL)
        {
            delete[] pszTmpFname;
            return -1;
        }

        // Write the specified feature into the temporary file.
        FILE* fpTemp = m_fp;
        m_fp = fp;
        int ret = (*handler)(data);
        m_fp = fpTemp;

        if (ret != 0)
        {
            VSIFClose(fp);
            remove(pszTmpFname);
            delete[] pszTmpFname;
            return -1;
        }

        // Write content from nEndPos to the temporary file
        char buffer[1024];
        int nBufferSize = sizeof(buffer)/sizeof(char);
        int nByteNum = nBufferSize;

        VSIFSeek(m_fp, nEndPos, SEEK_SET);
        while( nByteNum == nBufferSize )
        {
            nByteNum = VSIFRead(buffer, sizeof(char), nBufferSize, m_fp);
            VSIFWrite(buffer, sizeof(char), nByteNum, fp);
        }

        // Write back to the original file
        VSIRewind(fp);
        VSIFSeek(m_fp, nStartPos, SEEK_SET);
        nByteNum = nBufferSize;
        while( nByteNum == nBufferSize )
        {
            nByteNum = VSIFRead(buffer, sizeof(char), nBufferSize, fp);
            VSIFWrite(buffer, sizeof(char), nByteNum, m_fp);
        }

        // Delete temporary file
        VSIFClose(fp);
        remove(pszTmpFname);
        
        // We don't need to release pszTmpFname because it is one static memeory.
        //delete[] pszTmpFname;
    }

    long nNewFileLen = VSIFTell(m_fp);
    if (nNewFileLen < nFileLen)
        VSIChSize(m_fp, nNewFileLen);

    nLengthOffset = nNewFileLen - nFileLen;
    return 0;
}

int UpdateFeatureByTabFeature(void* data)
{
	MIDDATAFile::FeatureData* featureData = (MIDDATAFile::FeatureData*)data;
    if (featureData->isGeometry)
        return featureData->feature->WriteGeometryToMIFFile(featureData->file);
    else
        return featureData->feature->WriteRecordToMIDFile(featureData->file);
}

int UpdateFeatureByString(void* data)
{
	MIDDATAFile::StringData* stringData = (MIDDATAFile::StringData*)data;
	return VSIFWrite(stringData->stringData, sizeof(char), strlen(stringData->stringData), stringData->file);
}

int UpdateMIFHeader(void* data)
{
	MIFFile* file = (MIFFile*)data;
	return file->WriteMIFHeader();
}

int MIDDATAFile::UpdateFeature(long nStartPos, long nEndPos, TABFeature* feature, GBool bGeometry, GInt32& nLengthOffset)
{
    if (feature == NULL)
        return -1;
	
	FeatureData data;
	data.file = this;
	data.feature = feature;
	data.isGeometry = bGeometry;
	return UpdateFeatureInternal(nStartPos, nEndPos, UpdateFeatureByTabFeature, (void*)&data, nLengthOffset);
}

int MIDDATAFile::UpdateData(long nStartPos, long nEndPos, const char* stringData, GInt32& nLengthOffset)
{
    if (stringData == NULL)
        return -1;
	
	StringData data;
	data.file = m_fp;
	data.stringData = stringData;
	return UpdateFeatureInternal(nStartPos, nEndPos, UpdateFeatureByString, (void*)&data, nLengthOffset);
}

int MIDDATAFile::UpdateHeader(MIFFile* file, GInt32& nLengthOffset)
{
    if (file == NULL)
        return -1;
	
    GInt32 nPos = file->GetEndOfMIFHeader();
	return UpdateFeatureInternal(0, nPos, UpdateMIFHeader, (void*)file, nLengthOffset);
}

int MIDDATAFile::DeleteFeature(const PositionList& posList)
{
    if (posList.size() == 0)
        return 0;

    if (m_fp == NULL || m_eAccessMode != TABReadWrite)
        return -1;

    VSIFSeek(m_fp, 0L, SEEK_END);
    long nFileLen = VSIFTell(m_fp);

    // If deleting the last n features only, just move file pointer
    // to the starting pos and shorten file size.
    if (posList.size() == 1 && posList[0].second == nFileLen)
        VSIFSeek(m_fp, posList[0].first, SEEK_SET);
    else
    {
        // Create a temporary file to move data
        const char* pszTmpFname = CPLGenerateTempFilename(NULL);
        if (pszTmpFname == NULL)
            return -1;

        FILE* fp;
        if ((fp = VSIFOpen(pszTmpFname, "wt+")) == NULL)
        {
            delete[] pszTmpFname;
            return -1;
        }

        const char* pszLine = NULL;
        long nStartPos(-1), nEndPos(-1);
        for (size_t i = 0; i < posList.size(); ++i)
        {
            nStartPos = posList[i].second;
            if (i < posList.size() - 1)
                nEndPos = posList[i+1].first;
            else
                nEndPos = nFileLen;

            if (nStartPos <nEndPos && nStartPos < nFileLen && nEndPos <= nFileLen)
            {
                VSIFSeek(m_fp, nStartPos, SEEK_SET);
                while((pszLine = GetLine()) != NULL  && GetCurrentPos() <= nEndPos)
                    fprintf(fp, "%s\n", pszLine);
            }
        }

        // Write back to the original file
        char buffer[1024];
        int nBufferSize = sizeof(buffer)/sizeof(char);
        int nByteNum = nBufferSize;
        nByteNum = nBufferSize;
        nStartPos = posList[0].first;
        VSIRewind(fp);
        VSIFSeek(m_fp, nStartPos, SEEK_SET);
        while( nByteNum == nBufferSize )
        {
            nByteNum = VSIFRead(buffer, sizeof(char), nBufferSize, fp);
            VSIFWrite(buffer, sizeof(char), nByteNum, m_fp);
        }

        // Delete temporary file
        VSIFClose(fp);
        remove(pszTmpFname);
        
        // We don't need to release pszTmpFname because it is one static memeory.
        // delete[] pszTmpFname;
    }

    long nNewFileLen = VSIFTell(m_fp);
    if (nNewFileLen < nFileLen)
        VSIChSize(m_fp, nNewFileLen);

    return 0;
}

long MIDDATAFile::GetCurrentPos()
{
    if (m_fp == NULL)
        return -1;

    return VSIFTell(m_fp);
}

int MIDDATAFile::SetPos(long pos)
{
    if (m_fp == NULL)
        return -1;

    return VSIFSeek(m_fp, pos, SEEK_SET);
}

int MIDDATAFile::Close()
{
    if (m_fp == NULL)
        return 0;
   
    Flush();

    // Close file
    VSIFClose(m_fp);
    m_fp = NULL;

    // clear readline buffer.
    CPLReadLine( NULL );

    Reset();
    return 0;
}

int MIDDATAFile::Flush()
{
    if (m_eAccessMode == TABRead || m_fp == NULL)
        return 0;

    return VSIFFlush(m_fp);
}

int MIDDATAFile::IsFileReadOnly()
{
    if (NULL == m_pszFname || m_pszFname[0] == 0)
        return -1;

    return VSIIsFileReadOnly(m_pszFname);
}

int MIDDATAFile::GetFileInfo(FileInfo& fileinfo)
{
    if (NULL == m_pszFname || m_pszFname[0] == 0)
        return -1;

    VSIStatBuf  sStatBuf;
    if ( VSIStat(m_pszFname, &sStatBuf) == -1 )
        return -1;

    fileinfo.Size = sStatBuf.st_size;
    tm* tmp = gmtime(&(sStatBuf.st_mtime));
    if (tmp == NULL)
        return -1;

    fileinfo.Year = tmp->tm_year;
    fileinfo.Month = tmp->tm_mon;
    fileinfo.Day = tmp->tm_mday;
    fileinfo.Hour = tmp->tm_hour;
    fileinfo.Minute = tmp->tm_min;
    fileinfo.Second = tmp->tm_sec;

    return 0;
}

const char *MIDDATAFile::GetLine()
{
    const char *pszLine;
    
    if (m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite )
    {
        
        pszLine = CPLReadLine(m_fp);

        SetEof(VSIFEof(m_fp));

        if (pszLine == NULL)
        {
            m_szLastRead[0] = '\0';
        }
        else
        {
            strncpy(m_szLastRead,pszLine,MIDMAXCHAR);
        }
        //if (pszLine)
        //  printf("%s\n",pszLine);
        return pszLine;
    }
    else
      CPLAssert(FALSE);
    
    return NULL;
}

const char *MIDDATAFile::GetLastLine()
{
    // Return NULL if EOF
    if(GetEof())
    {
        return NULL;
    }
    else if (m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite)
    {
        // printf("%s\n",m_szLastRead);
        return m_szLastRead;
    }

    // We should never get here (Read/Write mode not implemented)
    CPLAssert(FALSE);
    return NULL;
}

void MIDDATAFile::WriteLine(const char *pszFormat,...)
{
    va_list args;

    if ((m_eAccessMode == TABWrite || m_eAccessMode == TABReadWrite) && m_fp)
    {
        va_start(args, pszFormat);
         vfprintf( m_fp, pszFormat, args );
        va_end(args);
    } 
    else
    {
        CPLAssert(FALSE);
    }
}


void MIDDATAFile::SetTranslation(double dfXMul,double dfYMul, 
                                 double dfXTran,
                                 double dfYTran)
{
    m_dfXMultiplier = dfXMul;
    m_dfYMultiplier = dfYMul;
    m_dfXDisplacement = dfXTran;
    m_dfYDisplacement = dfYTran;
}

double MIDDATAFile::GetXTrans(double dfX)
{
    return (dfX * m_dfXMultiplier) + m_dfXDisplacement;
}

double MIDDATAFile::GetYTrans(double dfY)
{
    return (dfY * m_dfYMultiplier) + m_dfYDisplacement;
}


GBool MIDDATAFile::IsValidFeature(const char *pszString)
{
    char **papszToken ;

    papszToken = CSLTokenizeString(pszString);
    
    //   printf("%s\n",pszString);

    if (CSLCount(papszToken) == 0)
    {
        CSLDestroy(papszToken);
        return FALSE;
    }

    if (EQUAL(papszToken[0],"NONE")      || EQUAL(papszToken[0],"POINT") ||
        EQUAL(papszToken[0],"LINE")      || EQUAL(papszToken[0],"PLINE") ||
        EQUAL(papszToken[0],"REGION")    || EQUAL(papszToken[0],"ARC") ||
        EQUAL(papszToken[0],"TEXT")      || EQUAL(papszToken[0],"RECT") ||
        EQUAL(papszToken[0],"ROUNDRECT") || EQUAL(papszToken[0],"ELLIPSE") ||
        EQUAL(papszToken[0],"MULTIPOINT")|| EQUAL(papszToken[0],"COLLECTION") )
    {
        CSLDestroy(papszToken);
        return TRUE;
    }

    CSLDestroy(papszToken);
    return FALSE;

}


GBool MIDDATAFile::GetEof()
{
    return m_bEof;
}


void MIDDATAFile::SetEof(GBool bEof)
{
    m_bEof = bEof;
}
